﻿using Microsoft.Pfe.Xrm;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Runtime.Caching;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;

namespace aier.xrm.wcf
{
    public class XrmService
    {
        private readonly string crmConnection = ConfigurationManager.ConnectionStrings["Aier_MSCRM"].ConnectionString;
        private readonly string username = string.Empty;
        private readonly string password = string.Empty;
        private readonly Uri serverUri = null;


        public XrmService()
        {
            if (!string.IsNullOrWhiteSpace(crmConnection))
            {
                string[] configs = crmConnection.Split(';');
                configs = configs.Where(t => t.Contains("Username")
                                              || t.Contains("Password")
                                              || t.Contains("Url")).ToArray();

                serverUri = new Uri(configs[0].Replace("Url=", string.Empty).Trim() + "/XRMServices/2011/Organization.svc");
                username = configs[1].Replace("Username=", string.Empty).Trim();
                password = configs[2].Replace("Password=", string.Empty).Trim();
            }
        }


        public void UseOrganizationServiceProxy(Action<OrganizationServiceProxy> action, Guid callId = default(Guid))
        {
            UseOrganizationServiceProxy(this.username, this.password, action, callId);
        }

        public T UseOrganizationServiceProxy<T>(Func<OrganizationServiceProxy, T> func, Guid callId = default(Guid))
        {
            return UseOrganizationServiceProxy(this.username, this.password, func, callId);
        }

        public void UseParallelOrganizationServiceManager(Action<OrganizationServiceManager> action, Guid callId = default(Guid))
        {
            UseParallelOrganizationServiceManager(this.username, this.password, action, callId);
        }

        public T UseParallelOrganizationServiceManager<T>(Func<OrganizationServiceManager, T> func, Guid callId = default(Guid))
        {
            return UseParallelOrganizationServiceManager(this.username, this.password, func, callId);
        }



        public T UseOrganizationServiceProxy<T>(string uname, string pwd, Func<OrganizationServiceProxy, T> func, Guid callId = default(Guid))
        {
            OrganizationServiceProxy organizationService = null;

            try
            {

                IServiceManagement<IOrganizationService> orgServiceManagement
                                                        = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(serverUri);

                AuthenticationCredentials tokenCredentials = AuthIFDCredentials(uname, pwd, orgServiceManagement);

                organizationService = new OrganizationServiceProxy(orgServiceManagement, tokenCredentials.SecurityTokenResponse);

                if (callId != Guid.Empty)
                    organizationService.CallerId = callId;

                return func(organizationService);

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                Console.WriteLine("The application terminated with an error.");
                Console.WriteLine("Timestamp: {0}", ex.Detail.Timestamp);
                Console.WriteLine("Code: {0}", ex.Detail.ErrorCode);
                Console.WriteLine("Message: {0}", ex.Detail.Message);
                Console.WriteLine("Trace: {0}", ex.Detail.TraceText);
                Console.WriteLine("Inner Fault: {0}",
                    null == ex.Detail.InnerFault ? "No Inner Fault" : "Has Inner Fault");
                throw ex;
            }
            catch (TimeoutException ex)
            {
                Console.WriteLine("The application terminated with an error.");
                Console.WriteLine("Message: {0}", ex.Message);
                Console.WriteLine("Stack Trace: {0}", ex.StackTrace);
                Console.WriteLine("Inner Fault: {0}",
                    null == ex.InnerException.Message ? "No Inner Fault" : ex.InnerException.Message);
                throw ex;
            }
            catch (Exception ex)
            {
                Console.WriteLine("The application terminated with an error.");
                Console.WriteLine(ex.Message);

                // Display the details of the inner exception.
                if (ex.InnerException != null)
                {
                    Console.WriteLine(ex.InnerException.Message);

                    FaultException<OrganizationServiceFault> fe = ex.InnerException
                        as FaultException<OrganizationServiceFault>;
                    if (fe != null)
                    {
                        Console.WriteLine("Timestamp: {0}", fe.Detail.Timestamp);
                        Console.WriteLine("Code: {0}", fe.Detail.ErrorCode);
                        Console.WriteLine("Message: {0}", fe.Detail.Message);
                        Console.WriteLine("Trace: {0}", fe.Detail.TraceText);
                        Console.WriteLine("Inner Fault: {0}",
                            null == fe.Detail.InnerFault ? "No Inner Fault" : "Has Inner Fault");
                    }
                }
                throw ex;
            }
            finally
            {
                if (organizationService != null)
                    organizationService.Dispose();
            }
        }

        public void UseOrganizationServiceProxy(string uname, string pwd, Action<OrganizationServiceProxy> action, Guid callId = default(Guid))
        {
            OrganizationServiceProxy organizationService = null;

            try
            {

                IServiceManagement<IOrganizationService> orgServiceManagement
                                                        = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(serverUri);

                AuthenticationCredentials tokenCredentials = AuthIFDCredentials(uname, pwd, orgServiceManagement);

                organizationService = new OrganizationServiceProxy(orgServiceManagement, tokenCredentials.SecurityTokenResponse);

                if (callId != Guid.Empty)
                    organizationService.CallerId = callId;

                action(organizationService);

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                Console.WriteLine("The application terminated with an error.");
                Console.WriteLine("Timestamp: {0}", ex.Detail.Timestamp);
                Console.WriteLine("Code: {0}", ex.Detail.ErrorCode);
                Console.WriteLine("Message: {0}", ex.Detail.Message);
                Console.WriteLine("Trace: {0}", ex.Detail.TraceText);
                Console.WriteLine("Inner Fault: {0}",
                    null == ex.Detail.InnerFault ? "No Inner Fault" : "Has Inner Fault");
                throw ex;
            }
            catch (TimeoutException ex)
            {
                Console.WriteLine("The application terminated with an error.");
                Console.WriteLine("Message: {0}", ex.Message);
                Console.WriteLine("Stack Trace: {0}", ex.StackTrace);
                Console.WriteLine("Inner Fault: {0}",
                    null == ex.InnerException.Message ? "No Inner Fault" : ex.InnerException.Message);
                throw ex;
            }
            catch (Exception ex)
            {
                Console.WriteLine("The application terminated with an error.");
                Console.WriteLine(ex.Message);

                // Display the details of the inner exception.
                if (ex.InnerException != null)
                {
                    Console.WriteLine(ex.InnerException.Message);

                    FaultException<OrganizationServiceFault> fe = ex.InnerException
                        as FaultException<OrganizationServiceFault>;
                    if (fe != null)
                    {
                        Console.WriteLine("Timestamp: {0}", fe.Detail.Timestamp);
                        Console.WriteLine("Code: {0}", fe.Detail.ErrorCode);
                        Console.WriteLine("Message: {0}", fe.Detail.Message);
                        Console.WriteLine("Trace: {0}", fe.Detail.TraceText);
                        Console.WriteLine("Inner Fault: {0}",
                            null == fe.Detail.InnerFault ? "No Inner Fault" : "Has Inner Fault");
                    }
                }
                throw ex;
            }
            finally
            {
                if (organizationService != null)
                    organizationService.Dispose();
            }
        }



        public void UseParallelOrganizationServiceManager(string uname, string pwd, Action<OrganizationServiceManager> action, Guid callId = default(Guid))
        {

            OrganizationServiceManager organizationServiceManager = null;

            try
            {
                IServiceManagement<IOrganizationService> orgServiceManagement
                                             = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(serverUri);

                AuthenticationCredentials tokenCredentials = new AuthenticationCredentials();

                tokenCredentials.ClientCredentials.UserName.UserName = uname;
                tokenCredentials.ClientCredentials.UserName.Password = pwd;
                organizationServiceManager = new OrganizationServiceManager(orgServiceManagement, tokenCredentials);
                if (callId != Guid.Empty)
                    organizationServiceManager.GetProxy().CallerId = callId;
                action(organizationServiceManager);

            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (organizationServiceManager != null && organizationServiceManager.GetProxy() != null)
                {
                    organizationServiceManager.GetProxy().Dispose();
                    organizationServiceManager = null;
                }
            }
        }


        public T UseParallelOrganizationServiceManager<T>(string uname, string pwd, Func<OrganizationServiceManager, T> func, Guid callId = default(Guid))
        {

            OrganizationServiceManager organizationServiceManager = null;

            try
            {
                IServiceManagement<IOrganizationService> orgServiceManagement
                                             = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(serverUri);

                AuthenticationCredentials tokenCredentials = new AuthenticationCredentials();

                tokenCredentials.ClientCredentials.UserName.UserName = uname;
                tokenCredentials.ClientCredentials.UserName.Password = pwd;
                organizationServiceManager = new OrganizationServiceManager(orgServiceManagement, tokenCredentials);
                if (callId != Guid.Empty)
                    organizationServiceManager.GetProxy().CallerId = callId;
                return func(organizationServiceManager);

            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (organizationServiceManager != null && organizationServiceManager.GetProxy() != null)
                {
                    organizationServiceManager.GetProxy().Dispose();
                    organizationServiceManager = null;
                }
            }
        }



        private AuthenticationCredentials AuthIFDCredentials(string uname, string pwd, IServiceManagement<IOrganizationService> orgServiceManagement)
        {
            ObjectCache oCache = MemoryCache.Default;
            AuthenticationCredentials authCredentials = null;
            string key = string.Concat(uname?.ToLower(), pwd, "ifd");
            if (oCache[key] as AuthenticationCredentials != null &&
                         (oCache[key] as AuthenticationCredentials).SecurityTokenResponse != null)
            {
                authCredentials = oCache[key] as AuthenticationCredentials;
            }
            else
            {
                authCredentials = new AuthenticationCredentials();
                authCredentials.ClientCredentials.UserName.UserName = uname;
                authCredentials.ClientCredentials.UserName.Password = pwd;
                authCredentials = orgServiceManagement.Authenticate(authCredentials);

                CacheItemPolicy policy = new CacheItemPolicy();
                policy.AbsoluteExpiration = DateTime.Now.AddMinutes(5);
                oCache.Set(key, authCredentials, policy);
            }
            return authCredentials;
        }



        #region  不在推荐使用

        public OrganizationServiceProxy GetOrganizationServiceProxy(Guid callId = default(Guid))
        {
            return GetOrganizationServiceProxy(this.username, this.password, callId);
        }

        public OrganizationServiceProxy GetOrganizationServiceProxy(string uname, string pwd, Guid callId = default(Guid))
        {
            OrganizationServiceProxy organizationService = null;

            try
            {

                IServiceManagement<IOrganizationService> orgServiceManagement
                                                        = ServiceConfigurationFactory.CreateManagement<IOrganizationService>(serverUri);

                AuthenticationCredentials tokenCredentials = AuthIFDCredentials(uname, pwd, orgServiceManagement);

                organizationService = new OrganizationServiceProxy(orgServiceManagement, tokenCredentials.SecurityTokenResponse);

                if (callId != Guid.Empty)
                    organizationService.CallerId = callId;

            }
            catch (FaultException<OrganizationServiceFault> ex)
            {
                Console.WriteLine("The application terminated with an error.");
                Console.WriteLine("Timestamp: {0}", ex.Detail.Timestamp);
                Console.WriteLine("Code: {0}", ex.Detail.ErrorCode);
                Console.WriteLine("Message: {0}", ex.Detail.Message);
                Console.WriteLine("Trace: {0}", ex.Detail.TraceText);
                Console.WriteLine("Inner Fault: {0}",
                    null == ex.Detail.InnerFault ? "No Inner Fault" : "Has Inner Fault");
                throw ex;
            }
            catch (TimeoutException ex)
            {
                Console.WriteLine("The application terminated with an error.");
                Console.WriteLine("Message: {0}", ex.Message);
                Console.WriteLine("Stack Trace: {0}", ex.StackTrace);
                Console.WriteLine("Inner Fault: {0}",
                    null == ex.InnerException.Message ? "No Inner Fault" : ex.InnerException.Message);
                throw ex;
            }
            catch (Exception ex)
            {
                Console.WriteLine("The application terminated with an error.");
                Console.WriteLine(ex.Message);

                // Display the details of the inner exception.
                if (ex.InnerException != null)
                {
                    Console.WriteLine(ex.InnerException.Message);

                    FaultException<OrganizationServiceFault> fe = ex.InnerException
                        as FaultException<OrganizationServiceFault>;
                    if (fe != null)
                    {
                        Console.WriteLine("Timestamp: {0}", fe.Detail.Timestamp);
                        Console.WriteLine("Code: {0}", fe.Detail.ErrorCode);
                        Console.WriteLine("Message: {0}", fe.Detail.Message);
                        Console.WriteLine("Trace: {0}", fe.Detail.TraceText);
                        Console.WriteLine("Inner Fault: {0}",
                            null == fe.Detail.InnerFault ? "No Inner Fault" : "Has Inner Fault");
                    }
                }
                throw ex;
            }

            return organizationService;
        }

        #endregion

    }
}
